TokenController.cs
[HttpGet]
public async Task<string> Get()
{
var authorizeService = HttpContext.RequestServices.GetRequiredService<IAuthorizationService>();
var authorizationResult = await authorizeService.AuthorizeAsync(HttpContext.User, null, new List<IAuthorizationRequirement>()
{
new RolesAuthorizationRequirement(new[] { "Admin" })
});
if(authorizationResult.Succeeded) return "Hello World";
Response.StatusCode = 403;
return "Forbidden";
}
昨天我們第一版的授權是透過IAuthorizationService.AuthorizeAsync
來做授權的
顧名思義,這個Service主要是做跟授權相關的事
public interface IAuthorizationService
{
Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object? resource, IEnumerable<IAuthorizationRequirement> requirements);
Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object? resource, string policyName);
}
提供了兩種授權方式
IAuthorizationRequirement
為基準的授權方式Policy
為基準的授權方式ClaimePrincipal
是正如我們傳入的HttpContext.User
代表的是使用者的身分
跟驗證類似,在授權完後會回傳一個Result: AuthorizationResult
一個非常單純的類別,只存驗證是否成功以及失敗的資訊
比較有趣的是不論成功或失敗只能夠透過靜態工廠方法產生該類別
public class AuthorizationResult
{
private AuthorizationResult() { }
public bool Succeeded { get; private set; }
public AuthorizationFailure? Failure { get; private set; }
public static AuthorizationResult Success() => new AuthorizationResult { Succeeded = true };
public static AuthorizationResult Failed(AuthorizationFailure failure) => new AuthorizationResult { Failure = failure };
public static AuthorizationResult Failed() => new AuthorizationResult { Failure = AuthorizationFailure.ExplicitFail() };
}
我們昨天有提到的RolesAuthorizationRequirement
就是一種IAuthoriztionRequirement
表示需要取得授權的資格,以昨天的例子來看需要有ClaimType.Role
為 Admin
的User才有辦法成功取得授權
IAuthoriztionRequirement
本身是個空介面,只是作為介面約束
public interface IAuthorizationRequirement
{
}
RolesAuthorizationRequirement
除了實作IAuthoriztionRequirement
外
還繼承了 AuthorizationHandler<TIAuthoriztionRequirement>
這個實作了 IAuthorizationHandler
的類別
IAuthorizationHandler.cs
public interface IAuthorizationHandler
{
/// <summary>
/// Makes a decision if authorization is allowed.
/// </summary>
/// <param name="context">The authorization information.</param>
Task HandleAsync(AuthorizationHandlerContext context);
}
我覺得驗證跟授權的設計概念頗相似,
授權一樣會透過Handler去處理 授權所需的上下文
且這一切都是透過IAuthorizeService
的實作所完成
有幾個比較常見的IAuthoriztionRequirement
這個物件非常的單純暴力
就是檢查要求的ClaimsType中有沒有對應的值
Ex.
var claimsAuthorizationRequirement = new ClaimsAuthorizationRequirement("my-claim", new[] { "Eric" });
帶帶入的ClaimIdentity中有 ClaimType 為 my-claim
, 且值為Eric
才能成功取得資源
ClaimsAuthorizationRequirement
public class ClaimsAuthorizationRequirement : AuthorizationHandler<ClaimsAuthorizationRequirement>, IAuthorizationRequirement
{
public ClaimsAuthorizationRequirement(string claimType, IEnumerable<string>? allowedValues)
{
if (claimType == null)
{
throw new ArgumentNullException(nameof(claimType));
}
ClaimType = claimType;
AllowedValues = allowedValues;
}
public string ClaimType { get; }
public IEnumerable<string>? AllowedValues { get; }
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ClaimsAuthorizationRequirement requirement)
{
if (context.User != null)
{
var found = false;
if (requirement.AllowedValues == null || !requirement.AllowedValues.Any())
{
found = context.User.Claims.Any(c => string.Equals(c.Type, requirement.ClaimType, StringComparison.OrdinalIgnoreCase));
}
else
{
found = context.User.Claims.Any(c => string.Equals(c.Type, requirement.ClaimType, StringComparison.OrdinalIgnoreCase)
&& requirement.AllowedValues.Contains(c.Value, StringComparer.Ordinal));
}
if (found)
{
context.Succeed(requirement);
}
}
return Task.CompletedTask;
}
}
這個物件所阻擋的是未經驗證使用者
也就是只要所有驗證方式(jwt, basic, cookie...)都不符合時就會被阻擋
根據使用者的ClaimsIdenties 中是否有Name為對應的Name以取得授權
public class NameAuthorizationRequirement : AuthorizationHandler<NameAuthorizationRequirement>, IAuthorizationRequirement
{
public NameAuthorizationRequirement(string requiredName)
{
if (requiredName == null)
{
throw new ArgumentNullException(nameof(requiredName));
}
RequiredName = requiredName;
}
public string RequiredName { get; }
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, NameAuthorizationRequirement requirement)
{
if (context.User != null)
{
if (context.User.Identities.Any(i => string.Equals(i.Name, requirement.RequiredName, StringComparison.Ordinal)))
{
context.Succeed(requirement);
}
}
return Task.CompletedTask;
}
}
RolesAuthorizationRequirement
一樣是去檢查ClaimIndentity中的Role中是否有滿足的Role
public class RolesAuthorizationRequirement : AuthorizationHandler<RolesAuthorizationRequirement>, IAuthorizationRequirement
{
public IEnumerable<string> AllowedRoles { get; }
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RolesAuthorizationRequirement requirement)
{
if (context.User != null)
{
bool found = false;
if (requirement.AllowedRoles == null || !requirement.AllowedRoles.Any())
{
// Review: What do we want to do here? No roles requested is auto success?
}
else
{
found = requirement.AllowedRoles.Any(r => context.User.IsInRole(r));
}
if (found)
{
context.Succeed(requirement);
}
}
return Task.CompletedTask;
}
}